#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate rocket_contrib;
use serde::Serialize;

mod model;
mod schema;

use diesel::SqliteConnection;
use rocket::Request;
use rocket::Rocket;
use rocket_contrib::json::Json;
use rocket_contrib::serve::StaticFiles;

use crate::model::{Comm, Comment, Task, Todo};

// This macro from `diesel_migrations` defines an `embedded_migrations` module
// containing a function named `run`. This allows the example to be run and
// tested without any outside setup of the database.
// embed_migrations!();

#[database("sqlite_database")]
pub struct DbConn(SqliteConnection);

#[derive(Serialize)]
struct Message {
    code: usize,
    info: String,
}

impl Message {
    fn success(info: &str) -> Self {
        Self {
            code: 200,
            info: info.into(),
        }
    }

    fn failed(info: &str) -> Self {
        Self {
            code: 400,
            info: info.into(),
        }
    }

    fn success_json(info: &str) -> Json<Message> {
        Json(Message::success(info))
    }

    fn failed_json(info: &str) -> Json<Message> {
        Json(Message::failed(info))
    }
}

// type Resp<T: Serialize> = Result<Json<T>, Json<Message>>;

#[get("/tasks")]
fn all_tasks(conn: DbConn) -> Json<Vec<Task>> {
    Json(Task::all(&conn))
}

#[post("/tasks", data = "<todo>")]
fn new_task(todo: Json<Todo>, conn: DbConn) -> Json<Message> {
    let todo = todo.0;
    if todo.description.is_empty() {
        Message::failed_json("description is null.")
    } else if Task::insert(todo, &conn) {
        Message::success_json("created.")
    } else {
        Message::failed_json("description is null.")
    }
}

#[put("/tasks/<id>")]
fn toggle_task(id: i32, conn: DbConn) -> Json<Message> {
    if Task::toggle_with_id(id, &conn) {
        Message::success_json("toogle done.")
    } else {
        Message::failed_json("toogle failed.")
    }
}

#[delete("/tasks/<id>")]
fn delete_task(id: i32, conn: DbConn) -> Json<Message> {
    if Task::delete_with_id(id, &conn) {
        Message::success_json("delete done.")
    } else {
        Message::failed_json("delete failed.")
    }
}

#[get("/tasks/<id>/comments")]
fn task_comments(id: i32, conn: DbConn) -> Json<Vec<Comment>> {
    Json(Task::comments(id, &conn))
}

#[post("/tasks/<id>/comments", format = "application/json", data = "<comm>")]
fn task_add_comment(id: i32, comm: Json<Comm>, conn: DbConn) -> Json<Message> {
    let comm = comm.0;
    if Task::add_comment(id, comm, &conn) {
        Message::success_json("success.")
    } else {
        Message::failed_json("failed.")
    }
}

#[delete("/comments/<cid>")]
fn task_delete_comment(cid: i32, conn: DbConn) -> Json<Message> {
    if Task::delete_comment_with_id(cid, &conn) {
        Message::success_json("delete done.")
    } else {
        Message::failed_json("delete failed.")
    }
}

#[catch(404)]
fn not_found(_: &Request) -> Json<Message> {
    Message::failed_json("Back Request.")
}

fn rocket() -> Rocket {
    let cors = rocket_cors::CorsOptions::default().to_cors().unwrap();
    rocket::ignite()
        .attach(DbConn::fairing())
        .mount("/", StaticFiles::from("static/"))
        .mount(
            "/tasks/api/v1",
            routes![
                all_tasks,
                new_task,
                toggle_task,
                delete_task,
                task_comments,
                task_add_comment,
                task_delete_comment
            ],
        )
        .register(catchers![not_found])
        .attach(cors)
}

fn main() {
    rocket().launch();
}
